﻿#include "OutputDevice.h"
#include <dsound.h>

#pragma comment(lib,"dsound.lib")
#pragma comment(lib,"dxguid.lib")

OutputDevice::OutputDevice()
{
	memset(_event, 0, sizeof(_event));
}

OutputDevice::~OutputDevice()
{
	stop();
	close();
}

/** 打开播放设备 */
bool OutputDevice::open(void* winId, int channels, int sample_rate, int bits_per_sample)
{
	IDirectSoundNotify8* dsNotify = nullptr;
	DSBPOSITIONNOTIFY dsPosNotify[MAX_AUDIO_BUF];
	
	/**
	* 参数的含义如下：
	* lpcGuidDevice：要创建的设备对象的GUID。可以指定为NULL，代表默认的播放设备。
	* ppDS8：返回的IDirectSound8对象的地址。
	* pUnkOuter：必须设为NULL。
	*/
	if (FAILED(DirectSoundCreate8(nullptr, &_ds, nullptr)))
	{
		return false;
	}

	/**
	* 参数的含义如下：
	* hwnd：应用程序窗口句柄。
	* dwLevel：支持以下几种级别。
	* DSSCL_EXCLUSIVE：与DSSCL_PRIORITY具有相同的作用。
	* DSSCL_NORMAL：正常的协调层级标志，其他程序可共享声卡设备进行播放。
	* DSSCL_PRIORITY：设置声卡设备为当前程序独占。
	* DSSCL_WRITEPRIMAR：可写主缓冲区，此时副缓冲区就不能进行播放处理，即不能将次缓冲区的数据送进混声器，再输出到主缓冲区上。这是最完全控制声音播放的方式。
	*/
	if (FAILED(_ds->SetCooperativeLevel((HWND)winId, DSSCL_NORMAL)))
	{
		return false;
	}

	DSBUFFERDESC dsBufferDesc;
	memset(&dsBufferDesc, 0, sizeof(dsBufferDesc));

	dsBufferDesc.dwSize = sizeof(dsBufferDesc);
	dsBufferDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2;
	dsBufferDesc.dwBufferBytes = MAX_AUDIO_BUF * BUFFERNOTIFYSIZE;
	dsBufferDesc.lpwfxFormat = (WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
	dsBufferDesc.lpwfxFormat->wFormatTag = WAVE_FORMAT_PCM;
	/* format type */
	(dsBufferDesc.lpwfxFormat)->nChannels = channels;
	/* number of channels (i.e. mono, stereo...) */
	(dsBufferDesc.lpwfxFormat)->nSamplesPerSec = sample_rate;
	/* sample rate */
	(dsBufferDesc.lpwfxFormat)->nAvgBytesPerSec = sample_rate * (bits_per_sample / 8) * channels;
	/* for buffer estimation */
	(dsBufferDesc.lpwfxFormat)->nBlockAlign = (bits_per_sample / 8) * channels;
	/* block size of data */
	(dsBufferDesc.lpwfxFormat)->wBitsPerSample = bits_per_sample;
	/* number of bits per sample of mono data */
	(dsBufferDesc.lpwfxFormat)->cbSize = 0;

	// Creates a sound buffer object to manage audio samples.
	HRESULT hr1;

	if (FAILED(_ds->CreateSoundBuffer(&dsBufferDesc, &_dsBuffer, nullptr))) 
	{
		return false;
	}
	
	if (FAILED(_dsBuffer->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*)&_dsBuffer8))) 
	{
		return false;
	}

	// Get IDirectSoundNotify8
	if (FAILED(_dsBuffer8->QueryInterface(IID_IDirectSoundNotify, (LPVOID*)&dsNotify))) 
	{
		return false;
	}

	for (int i = 0; i < MAX_AUDIO_BUF; i++) 
	{
		dsPosNotify[i].dwOffset = i * BUFFERNOTIFYSIZE;
		_event[i] = CreateEvent(nullptr, false, false, nullptr);
		dsPosNotify[i].hEventNotify = _event[i];
	}

	dsNotify->SetNotificationPositions(MAX_AUDIO_BUF, dsPosNotify);
	dsNotify->Release();

    return true;
}

/** 关闭 */
void OutputDevice::close()
{
	if (_dsBuffer8)
	{
		_dsBuffer8->Release();
		_dsBuffer8 = nullptr;
	}

	if (_dsBuffer)
	{
		_dsBuffer->Release();
		_dsBuffer = nullptr;
	}

	if (_ds)
	{
		_ds->Release();
		_ds = nullptr;
	}
}

/** 播放 */
void OutputDevice::start()
{
	if (_isPlaying)
	{
		return;
	}
	_isPlaying = true;
	_playThead = new std::thread(&OutputDevice::play, this);
}

/** 停止播放 */
void OutputDevice::stop()
{
	_isPlaying = false;

	if (_playThead)
	{
		_playThead->join();
		delete _playThead;
		_playThead = nullptr;
	}
}

void OutputDevice::setSoundCallBack(SoundCallBack& callBack)
{
	_soundCallBack = callBack;
}

/** 播放线程 */
void OutputDevice::play()
{
	// Start Playing
	LPVOID buffer = nullptr;
	DWORD size = 0;
	DWORD result = WAIT_OBJECT_0;
	DWORD offset = BUFFERNOTIFYSIZE;

	_dsBuffer8->SetCurrentPosition(0);
	_dsBuffer8->Play(0, 0, DSBPLAY_LOOPING);

	while (_isPlaying)
	{
		if (result >= WAIT_OBJECT_0 && result <= WAIT_OBJECT_0 + 3)
		{
			_dsBuffer8->Lock(offset, BUFFERNOTIFYSIZE, &buffer, &size, nullptr, nullptr, 0);

			// 取数据
			if (_soundCallBack)
			{
				_soundCallBack(buffer, size);
			}

			offset += size;
			offset %= (BUFFERNOTIFYSIZE * MAX_AUDIO_BUF);
			_dsBuffer8->Unlock(buffer, size, nullptr, 0);
		}
		result = WaitForMultipleObjects(MAX_AUDIO_BUF, _event, FALSE, INFINITE);
	}

	_dsBuffer8->Stop();
}
 